package fons.model.query;

import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import com.google.common.collect.Lists;

import fons.model.ontology.Context;
import fons.model.ontology.AbstractContextDependent;
import fons.model.ontology.Metric;
import fons.model.ontology.Relation;
import fons.model.ontology.language.Literal;
import fons.model.ontology.language.MetricSentenceElement;
import fons.model.ontology.language.ModifierSentenceElement;
import fons.model.ontology.language.RelationSentenceElement;
import fons.model.ontology.language.Sentence;
import fons.model.ontology.language.SentenceElement;
import fons.model.ontology.language.SuffixTreeWordFinder;
import fons.model.ontology.language.Vocabulary;
import fons.model.ontology.language.WordFinder;
import fons.model.query.fun.Function;
import fons.model.query.fun.Functions;
import fons.model.query.fun.ListFun;
import fons.model.query.fun.Number;
import fons.model.query.fun.ReferenceFun;
import fons.model.query.fun.Scope;

public class Analyzer extends AbstractContextDependent {
	public Analyzer(Context context) {
		super(context);
	}

	public Answer analyze (Sentence sentence, Vocabulary vocabulary) {

		WordFinder finder = new SuffixTreeWordFinder();
		sentence.setElements(finder.getElements(sentence, vocabulary));

		for (SentenceElement element: sentence.getElements()) {
			System.out.println("Found element: " + element + " in sentence at \"..." + element.getText() + "...\"");
		}

		applyModifiersOn(sentence);
		List<Predicate> predicates = getApplicablePredicates(sentence);
		applyModifiersOn(sentence);

		List<Metric> metrics = getApplicableMetrics(sentence);

		Answer answer = new Answer();

		if (metrics.size() == 0) {
			Metric identity = new Metric("identity");
			ReferenceFun fun = new ReferenceFun("~");
			identity.addArgument(fun);
			identity.setFunction(Functions.get("sort").apply(Functions.get("more")).apply(fun));
			metrics.add(identity);
		}

		for (Metric metric: metrics) {
			for (Predicate predicate: predicates) {
				Scope scope = new Scope();
				Function p = predicate.getNumbers();
				scope.set("~", p);						

				prepare(metric, scope);

				for (ReferenceFun ref: metric.getUnboundArguments()) {
					System.out.println("Unbound for metric " + metric + ": " + ref.getName());

					if (metric.getTypeOf(ref).equals("Number")) {
						SentenceElement next = sentence.getElementAfter(metric);
						if (next.holds(Metric.class)) {
							System.out.println("Metric: " + metric.getName() + " " + next.get(Metric.class).getName());
							scope.set(ref.getName(), prepare(next.get(Metric.class), scope).getFunction());
							System.out.println("Function:\n" + metric.getFunction().toString() + "\n\n");
							metrics.remove(next.get(Metric.class));

						} else if (next.holds(Literal.class)) {
							System.out.println("Metric: " + metric.getName() + " " + next.get(Literal.class).value);
							scope.set(ref.getName(), 
									new Number(sentence.getElementAfter(metric).get(Literal.class).value));
						}
					}
				}
				
				Function fun = metric.apply();
				if (fun instanceof ListFun) {
					answer.add(predicate, (ListFun) fun);
				} else if (fun instanceof fons.model.query.fun.Number) {
					answer.add(predicate, (fons.model.query.fun.Number) fun);
				}
			}
		}
		answer.buildTable();		
		return answer;
	}

	private Metric prepare (Metric metric, Scope scope) {
		metric.setScope(scope);	
		return metric;
	}

	private List<Metric> getApplicableMetrics(Sentence sentence) {
		List<Metric> metrics = Lists.newArrayList();
		for (MetricSentenceElement metricElement: sentence.getElementsOf(MetricSentenceElement.class)) {
			metrics.add(metricElement.get());
		}
		return metrics;
	}

	private void applyModifiersOn(Sentence sentence) {
		List<ModifierSentenceElement> modifiers = Lists.newLinkedList(sentence.getElementsOf(ModifierSentenceElement.class));
		Collections.sort(modifiers, new Comparator<ModifierSentenceElement>() {
			@Override
			public int compare(ModifierSentenceElement arg0,
					ModifierSentenceElement arg1) {
				return arg0.get().compareTo(arg1.get());
			}

		});

		for (ModifierSentenceElement modifierElement: modifiers) {
			modifierElement.applyModifier(sentence);
		}
	}

	private List<Predicate> getApplicablePredicates(Sentence sentence) {
		List<Predicate> result = Lists.newLinkedList();

		for (RelationSentenceElement relation: sentence.getElementsOf(RelationSentenceElement.class)) {
			final Predicate predicate = new Predicate(relation.get());
			if (predicate.applyOn(sentence, relation)) {
				System.out.println("Predicate applies: " + predicate);
				result.add(predicate);
			}
		}

		if (result.size() == 0) {
			result.addAll(getApplicableImplicitPredicates(sentence));
		}

		return result;
	}

	private Collection<? extends Predicate> getApplicableImplicitPredicates(Sentence sentence) {
		List<Predicate> result = Lists.newLinkedList();
		for (Relation relation: getContext().getRelations()) {
			final Predicate predicate = new Predicate(relation);
			if (predicate.applyOn(sentence, null)) {
				System.out.println("Implicit predicate applies: " + predicate);
				result.add(predicate);
			}
		}
		return result;
	}


}
